﻿using Abp.Migration;
using System;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.ModelConfiguration.Configuration;
using System.Data.Entity.Spatial;
using System.Linq.Expressions;
namespace Abp.EntityFramework
{
    /// <summary>
    /// EntityFramework 实体映射实现
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class EfEntityMapper<T> : EntityTypeConfiguration<T>, IEntityMapper<T> where T : class
    {
        public string TableName { get; set; }
        public string SchemaName { get; set; }
        public string IdName { get; set; }
        public Dictionary<string, Tuple<string, string, string, string, string, string>> ForeignKeyConfigurationCollection { get; set; }
        public List<Tuple<string, string, string, string>> AnnotationConfigurationCollection { get; set; }
        public EfEntityMapper()
        {
            this.TableName = typeof(T).Name;
            ForeignKeyConfigurationCollection = new Dictionary<string, Tuple<string, string, string, string, string, string>>();
            AnnotationConfigurationCollection = new List<Tuple<string, string, string, string>>();
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
              this.SchemaName,
              this.TableName,
              null,
              EntityDescriptionUtils.GetTableDescription<T>()));
        }
        public void ForeignKey<TEntityMapper>(Expression<Func<T, object>> memberExpression, string foreignKeyName = "") where TEntityMapper : IEntityMapper
        {
            var entityMapper = Activator.CreateInstance<TEntityMapper>();
            if (string.IsNullOrWhiteSpace(this.IdName))
                throw new Exception(string.Format("{0}没有配置主键。", this.TableName));
            if (string.IsNullOrWhiteSpace(entityMapper.IdName))
                throw new Exception(string.Format("{0}没有配置主键。", entityMapper.TableName));
            string columnName = GetColumnName(memberExpression);
            if (string.IsNullOrWhiteSpace(foreignKeyName) || ForeignKeyConfigurationCollection.ContainsKey(foreignKeyName))
                foreignKeyName = string.Format("FK_{0}_{1}_{2}_Ref_{3}_{4}_{5}", this.SchemaName, this.TableName, columnName, entityMapper.SchemaName, entityMapper.TableName, entityMapper.IdName);
            ForeignKeyConfigurationCollection[foreignKeyName] = new Tuple<string, string, string, string, string, string>(
                this.SchemaName,
                this.TableName,
                columnName,
                entityMapper.SchemaName,
                entityMapper.TableName,
                entityMapper.IdName);
        }
        public new EntityTypeConfiguration<T> HasKey<TKey>(Expression<Func<T, TKey>> keyExpression)
        {
            this.IdName = GetColumnName(keyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
                 this.SchemaName,
                 this.TableName,
                 IdName,
                 EntityDescriptionUtils.GetColumnDescription<T>(IdName)));
            return base.HasKey(keyExpression);
        }
        public new EntityTypeConfiguration<T> ToTable(string tableName)
        {
            this.TableName = tableName;
            return base.ToTable(tableName);
        }
        public new EntityTypeConfiguration<T> ToTable(string tableName, string schemaName)
        {
            this.TableName = tableName;
            this.SchemaName = schemaName;
            return base.ToTable(tableName, schemaName);
        }
        public new DateTimePropertyConfiguration Property(Expression<Func<T, TimeSpan?>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new DateTimePropertyConfiguration Property(Expression<Func<T, TimeSpan>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new DateTimePropertyConfiguration Property(Expression<Func<T, DateTimeOffset?>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new DateTimePropertyConfiguration Property(Expression<Func<T, DateTimeOffset>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new DateTimePropertyConfiguration Property(Expression<Func<T, DateTime?>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new DecimalPropertyConfiguration Property(Expression<Func<T, decimal?>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new DecimalPropertyConfiguration Property(Expression<Func<T, decimal>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new BinaryPropertyConfiguration Property(Expression<Func<T, byte[]>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new StringPropertyConfiguration Property(Expression<Func<T, string>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new PrimitivePropertyConfiguration Property(Expression<Func<T, DbGeography>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new PrimitivePropertyConfiguration Property(Expression<Func<T, DbGeometry>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new PrimitivePropertyConfiguration Property<TKey>(Expression<Func<T, TKey?>> propertyExpression) where TKey : struct
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new DateTimePropertyConfiguration Property(Expression<Func<T, DateTime>> propertyExpression)
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        public new PrimitivePropertyConfiguration Property<TKey>(Expression<Func<T, TKey>> propertyExpression) where TKey : struct
        {
            string columnName = GetColumnName(propertyExpression);
            AnnotationConfigurationCollection.Add(new Tuple<string, string, string, string>(
               this.SchemaName,
               this.TableName,
               columnName,
               EntityDescriptionUtils.GetColumnDescription<T>(columnName)));
            return base.Property(propertyExpression);
        }
        private string GetColumnName<TKey>(Expression<Func<T, TKey>> memberExpression)
        {
            return memberExpression.Body.ToString().Split('.')[1].TrimEnd(')');
        }
    }
}
